Appearance
14. render props
本节目标:理解“14. render props”的核心思路,并能把它用于实际开发或面试表达。
render-propsrender prop是指一种在React组件之间使用一个值为函数的prop共享代码的简单技术具有
render prop的组件接受一个函数,该函数返回一个React元素并调用它而不是实现自己的渲染逻辑render prop是一个用于告知组件需要渲染什么内容的函数prop这也是逻辑复用的一种方式
14.1 原生实现
import React from 'react';import ReactDOM from 'react-dom';class MouseTracker extends React.Component { constructor(props) { super(props); this.state = { x: 0, y: 0 }; }
handleMouseMove = (event) => { this.setState({ x: event.clientX, y: event.clientY }); }
render() { return ( <div onMouseMove={this.handleMouseMove}> <h1>移动鼠标
!</h1> <p>当前的鼠标位置是
({this.state.x}, {this.state.y})</p> </div> ); }}ReactDOM.render(<MouseTracker />, document.getElementById('root'));
14.2 childrenchildren是一个渲染的方法
import React from './react';import ReactDOM from './react-dom';
class MouseTracker extends React.Component { constructor(props) { super(props); this.state = { x: 0, y: 0 }; }
handleMouseMove = (event) => { this.setState({ x: event.clientX, y: event.clientY }); }
render() { return ( <div onMouseMove={this.handleMouseMove}> {this.props.children(this.state)} </div> ); }}ReactDOM.render(<MouseTracker > { (props) => ( <div> <h1>移动鼠标
!</h1> <p>当前的鼠标位置是
({props.x}, {props.y})</p> </div> ) }</MouseTracker >, document.getElementById('root'));
14.3 render属性
import React from 'react';import ReactDOM from 'react-dom';class MouseTracker extends React.Component { constructor(props) { super(props); this.state = { x: 0, y: 0 }; }
handleMouseMove = (event) => { this.setState({ x: event.clientX, y: event.clientY }); }
render() { return ( <div onMouseMove={this.handleMouseMove}> {this.props.render(this.state)} </div> ); }}
ReactDOM.render(< MouseTracker render={params => ( <> <h1>移动鼠标
!</h1> <p>当前的鼠标位置是
({params.x}, {params.y})</p> </>)} />, document.getElementById('root'));
14.4 HOC
import React from 'react';import ReactDOM from 'react-dom';function withTracker(OldComponent){ return class MouseTracker extends React.Component{ constructor(props){ super(props); this.state = {x:0,y:0}; } handleMouseMove = (event)=>{ this.setState({ x:event.clientX, y:event.clientY }); } render(){ return ( <div onMouseMove = {this.handleMouseMove}> <OldComponent {...this.state}/> </div> ) } }}//renderfunction Show(props){ return ( <React.Fragment> <h1>请移动鼠标
</h1> <p>当前鼠标的位置是
: x:{props.x} y:{props.y}</p> </React.Fragment> )}let HighShow = withTracker(Show);ReactDOM.render( <HighShow/>, document.getElementById('root'));
15.性能优化
15.1 src\index.js
import React from './react';import ReactDOM from './react-dom';class ClassCounter extends React.PureComponent { render() { console.log('ClassCounter render'); return <div>ClassCounter:{this.props.count}</div> }}function FunctionCounter(props) { console.log('FunctionCounter render'); debugger return <div>FunctionCounter:{props.count}</div>}const MemoFunctionCounter = React.memo(FunctionCounter);class App extends React.Component { state = { number: 0 } amountRef = React.createRef() handleClick = () => { let nextNumber = this.state.number + parseInt(this.amountRef.current.value); this.setState({ number: nextNumber }); } render() { return ( <div> <ClassCounter count={this.state.number} /> <MemoFunctionCounter count={this.state.number} /> <input ref={this.amountRef} /> <button onClick={this.handleClick}>+</button> </div> ) }}ReactDOM.render( <App />, document.getElementById('root'));
15.2 src\constants.js
src\constants.js
export const REACT_TEXT = Symbol('REACT_TEXT');export const REACT_FORWARD_REF_TYPE = Symbol('react.forward_ref');
export const PLACEMENT = 'PLACEMENT';export const MOVE = 'MOVE';
export const REACT_CONTEXT = Symbol('react.context');export const REACT_PROVIDER = Symbol('react.provider');+export const REACT_MEMO = Symbol('react.memo')
15.3 src\utils.js
src\utils.js
import { REACT_TEXT } from "./constants";export function wrapToVdom(element) { return typeof element === "string" || typeof element === "number" ? { type: REACT_TEXT, props: { content: element } } : element;}
export function isFunction(obj) { return typeof obj === 'function';}
+export function shallowEqual(obj1, obj2) {+ if (obj1 === obj2) {+ return true;+ }+ if (typeof obj1 != "object" || obj1 === null || typeof obj2 != "object" || obj2 === null) {+ return false;+ }+ let keys1 = Object.keys(obj1);+ let keys2 = Object.keys(obj2);+ if (keys1.length !== keys2.length) {+ return false;+ }+ for (let key of keys1) {+ if (!obj2.hasOwnProperty(key) || obj1[key] !== obj2[key]) {+ return false;+ }+ }+ return true;+}
15.4 src\react.js
src\react.js
+import { wrapToVdom, shallowEqual } from "./utils";import { Component } from './Component';+import { REACT_FORWARD_REF_TYPE, REACT_CONTEXT, REACT_PROVIDER, REACT_MEMO } from './constants';function createElement(type, config, children) { let ref; let key; if (config) { delete config.__source; delete config.__self; ref = config.ref; delete config.ref; key = config.key; delete config.key; } let props = { ...config }; if (arguments.length > 3) { props.children = Array.prototype.slice.call(arguments, 2).map(wrapToVdom); } else { props.children = wrapToVdom(children); } return { type, ref, key, props, };}function createRef() { return { current: null };}function forwardRef(render) { var elementType = { $$typeof: REACT_FORWARD_REF_TYPE, render: render }; return elementType;}function Fragment(props) { return props.children;}function createContext() { let context = { $$typeof: REACT_CONTEXT }; context.Provider = { $$typeof: REACT_PROVIDER, _context: context } context.Consumer = { $$typeof: REACT_CONTEXT, _context: context } return context;}function cloneElement(element, newProps, ...newChildren) { let oldChildren = element.props && element.props.children; let children = [...(Array.isArray(oldChildren) ? oldChildren : [oldChildren]), ...newChildren] .filter(item => item !== undefined) .map(wrapToVdom); if (children.length === 1) children = children[0]; let props = { ...element.props, ...newProps, children }; return { ...element, props };}+class PureComponent extends Component {+ shouldComponentUpdate(newProps, nextState) {+ return !shallowEqual(this.props, newProps) || !shallowEqual(this.state, nextState);+ }+}+function memo(type, compare = shallowEqual) {+ return {+ $$typeof: REACT_MEMO,+ type,+ compare+ }+}const React = { createElement, Component, createRef, forwardRef, Fragment, createContext, cloneElement, PureComponent,+ memo};export default React;
15.5 src\react-dom.js
src\react-dom.js
+import { REACT_TEXT, REACT_FORWARD_REF_TYPE, PLACEMENT, MOVE, REACT_PROVIDER, REACT_CONTEXT, REACT_MEMO } from "./constants";import { addEvent } from "./event";import React from './react';function render(vdom, container) { mount(vdom, container);}export function mount(vdom, container) { let newDOM = createDOM(vdom); container.appendChild(newDOM); if (newDOM.componentDidMount) newDOM.componentDidMount();}export function createDOM(vdom) { let { type, props, ref } = vdom; let dom;+ if (type && type.$$typeof === REACT_MEMO) {+ return mountMemoComponent(vdom);+ } else if (type && type.$$typeof === REACT_PROVIDER) { return mountProviderComponent(vdom) } else if (type && type.$$typeof === REACT_CONTEXT) { return mountContextComponent(vdom) } else if (type && type.$$typeof === REACT_FORWARD_REF_TYPE) { return mountForwardComponent(vdom); } else if (type === REACT_TEXT) { dom = document.createTextNode(props.content); } else if (typeof type === "function") { if (type.isReactComponent) { return mountClassComponent(vdom); } else { if (type === React.Fragment) { dom = document.createDocumentFragment(); } else { return mountFunctionComponent(vdom); } } } else { dom = document.createElement(type); } if (props) { updateProps(dom, {}, props); if (typeof props.children == "object" && props.children.type) { props.children.mountIndex = 0; mount(props.children, dom); } else if (Array.isArray(props.children)) { reconcileChildren(props.children, dom); } } vdom.dom = dom; if (ref) ref.current = dom; return dom;}
+function mountMemoComponent(vdom) {+ let { type, props } = vdom;+ let renderVdom = type.type(props);+ vdom.prevProps = props;+ vdom.oldRenderVdom = renderVdom;+ return createDOM(renderVdom);+}function mountProviderComponent(vdom) { let { type, props } = vdom; let context = type._context; context._currentValue = props.value; let renderVdom = props.children; vdom.oldRenderVdom = renderVdom; return createDOM(renderVdom);}function mountContextComponent(vdom) { let { type, props } = vdom; let context = type._context; let renderVdom = props.children(context._currentValue); vdom.oldRenderVdom = renderVdom; return createDOM(renderVdom);}function mountForwardComponent(vdom) { let { type, props, ref } = vdom; let renderVdom = type.render(props, ref); vdom.oldRenderVdom = renderVdom; return createDOM(renderVdom);}function mountClassComponent(vdom) { let { type, props, ref } = vdom; let classInstance = new type(props); if (type.contextType) { classInstance.context = type.contextType._currentValue; } vdom.classInstance = classInstance; if (ref) ref.current = classInstance; if (classInstance.componentWillMount) classInstance.componentWillMount(); let renderVdom = classInstance.render(); classInstance.oldRenderVdom = vdom.oldRenderVdom = renderVdom; let dom = createDOM(renderVdom); if (classInstance.componentDidMount) dom.componentDidMount = classInstance.componentDidMount.bind(classInstance); return dom;}function mountFunctionComponent(vdom) { let { type, props } = vdom; let renderVdom = type(props); vdom.oldRenderVdom = renderVdom; return createDOM(renderVdom);}function updateProps(dom, oldProps, newProps) { for (let key in newProps) { if (key === "children") { continue; } if (key === "style") { let style = newProps[key]; for (let attr in style) { dom.style[attr] = style[attr]; } } else if (key.startsWith("on")) { addEvent(dom, key.toLocaleLowerCase(), newProps[key]); } else { dom[key] = newProps[key]; } }}export function findDOM(vdom) { let { type } = vdom; let dom;+ if (type && type.$$typeof === REACT_MEMO) {+ dom = findDOM(vdom.oldRenderVdom); } else if (type && type.$$typeof === REACT_PROVIDER) { dom = findDOM(vdom.oldRenderVdom); } else if (type && type.$$typeof === REACT_CONTEXT) { dom = findDOM(vdom.oldRenderVdom); } else if (typeof type === "function") { if (type === React.Fragment) { return vdom.dom; } else { dom = findDOM(vdom.oldRenderVdom); } } else { dom = vdom.dom; } return dom;}function unMount(vdom) { let currentDOM = findDOM(vdom); if (vdom.classInstance && vdom.classInstance.componentWillUnmount) { vdom.classInstance.componentWillUnmount(); } if (vdom.props && vdom.props.ref) { vdom.props.ref.current = null; } Object.keys(vdom.props).forEach((propName) => { if (propName.slice(0, 2) === "on") { const eventName = propName.toLowerCase().slice(0, 2); currentDOM.removeEventListener(eventName, vdom.props[propName]); } }); if (vdom.props.children) { let children = Array.isArray(vdom.props.children) ? vdom.props.children : [vdom.props.children]; children.forEach(unMount); } currentDOM.parentNode.removeChild(currentDOM);}export function compareTwoVdom(parentDOM, oldVdom, newVdom, nextDOM) { if (!oldVdom && !newVdom) { //老和新都是没有
return; } else if (!!oldVdom && !newVdom) { //老有新没有
unMount(oldVdom); } else if (!oldVdom && !!newVdom) { //老没有新的有
let newDOM = createDOM(newVdom); if (nextDOM) parentDOM.insertBefore(newDOM, nextDOM); else parentDOM.appendChild(newDOM); if (newDOM.componentDidMount) newDOM.componentDidMount(); return; } else if (!!oldVdom && !!newVdom && oldVdom.type !== newVdom.type) { //新老都有,但类型不同
let newDOM = createDOM(newVdom); unMount(oldVdom); if (newDOM.componentDidMount) newDOM.componentDidMount(); } else { updateElement(oldVdom, newVdom); }}function updateElement(oldVdom, newVdom) {+ if (oldVdom.type && oldVdom.type.$$typeof === REACT_MEMO) {+ updateMemoComponent(oldVdom, newVdom); } else if (oldVdom.type.$$typeof === REACT_CONTEXT) { updateContextComponent(oldVdom, newVdom); } else if (oldVdom.type.$$typeof === REACT_PROVIDER) { updateProviderComponent(oldVdom, newVdom); } else if (oldVdom.type === REACT_TEXT && newVdom.type === REACT_TEXT) { let currentDOM = newVdom.dom = findDOM(oldVdom); if (oldVdom.props.content !== newVdom.props.content) { currentDOM.textContent = newVdom.props.content; } return; } else if (typeof oldVdom.type === 'string') { let currentDOM = newVdom.dom = findDOM(oldVdom); updateProps(currentDOM, oldVdom.props, newVdom.props); updateChildren(currentDOM, oldVdom.props.children, newVdom.props.children); } else if (typeof oldVdom.type === 'function') { if (oldVdom.type.isReactComponent) { newVdom.classInstance = oldVdom.classInstance; updateClassInstance(oldVdom, newVdom); } else { if (oldVdom.type === React.Fragment) { let currentDOM = newVdom.dom = findDOM(oldVdom); updateChildren(currentDOM, oldVdom.props.children, newVdom.props.children); } else { updateFunctionComponent(oldVdom, newVdom); } } }}+function updateMemoComponent(oldVdom, newVdom) {+ let { type, prevProps } = oldVdom;+ if (!type.compare(prevProps, newVdom.props)) {+ let oldDOM = findDOM(oldVdom);+ let parentDOM = oldDOM.parentNode;+ let { type, props } = newVdom;+ let renderVdom = type.type(props);+ compareTwoVdom(parentDOM, oldVdom.oldRenderVdom, renderVdom);+ newVdom.prevProps = props;+ newVdom.oldRenderVdom = renderVdom;+ } else {+ newVdom.prevProps = prevProps;+ newVdom.oldRenderVdom = oldVdom.oldRenderVdom;+ }+}function updateProviderComponent(oldVdom, newVdom) { let parentDOM = findDOM(oldVdom).parentNode; let { type, props } = newVdom; let context = type._context; context._currentValue = props.value; let renderVdom = props.children; compareTwoVdom(parentDOM, oldVdom.oldRenderVdom, renderVdom); newVdom.oldRenderVdom = renderVdom;}function updateContextComponent(oldVdom, newVdom) { let parentDOM = findDOM(oldVdom).parentNode; let { type, props } = newVdom; let context = type._context; let renderVdom = props.children(context._currentValue); compareTwoVdom(parentDOM, oldVdom.oldRenderVdom, renderVdom); newVdom.oldRenderVdom = renderVdom;}function updateFunctionComponent(oldVdom, newVdom) { let parentDOM = findDOM(oldVdom).parentNode; let { type, props } = newVdom; let newRenderVdom = type(props); compareTwoVdom(parentDOM, oldVdom.oldRenderVdom, newRenderVdom); newVdom.oldRenderVdom = newRenderVdom;}function updateClassInstance(oldVdom, newVdom) { let classInstance = newVdom.classInstance = oldVdom.classInstance; newVdom.oldRenderVdom = oldVdom.oldRenderVdom; if (classInstance.componentWillReceiveProps) { classInstance.componentWillReceiveProps(); } classInstance.updater.emitUpdate(newVdom.props);}function updateChildren(parentDOM, oldVChildren, newVChildren) { oldVChildren = Array.isArray(oldVChildren) ? oldVChildren : oldVChildren ? [oldVChildren] : []; newVChildren = Array.isArray(newVChildren) ? newVChildren : newVChildren ? [newVChildren] : []; let keyedOldMap = {}; let lastPlacedIndex = 0; oldVChildren.forEach((oldVChild, index) => { let oldKey = oldVChild.key ? oldVChild.key : index; keyedOldMap[oldKey] = oldVChild; }); let patch = []; newVChildren.forEach((newVChild, index) => { newVChild.mountIndex = index; let newKey = newVChild.key ? newVChild.key : index; let oldVChild = keyedOldMap[newKey]; if (oldVChild) { updateElement(oldVChild, newVChild); if (oldVChild.mountIndex < lastPlacedIndex) { patch.push({ type: MOVE, oldVChild, newVChild, mountIndex: index }); } delete keyedOldMap[newKey]; lastPlacedIndex = Math.max(lastPlacedIndex, oldVChild.mountIndex); } else { patch.push({ type: PLACEMENT, newVChild, mountIndex: index }); } }); let moveVChild = patch.filter(action => action.type === MOVE).map(action => action.oldVChild); Object.values(keyedOldMap).concat(moveVChild).forEach((oldVChild) => { let currentDOM = findDOM(oldVChild); parentDOM.removeChild(currentDOM); }); patch.forEach(action => { let { type, oldVChild, newVChild, mountIndex } = action; let childNodes = parentDOM.childNodes; if (type === PLACEMENT) { let newDOM = createDOM(newVChild); let childNode = childNodes[mountIndex]; if (childNode) { parentDOM.insertBefore(newDOM, childNode); } else { parentDOM.appendChild(newDOM); } } else if (type === MOVE) { let oldDOM = findDOM(oldVChild); let childNode = childNodes[mountIndex]; if (childNode) { parentDOM.insertBefore(oldDOM, childNode); } else { parentDOM.appendChild(oldDOM); } } });}function reconcileChildren(childrenVdom, parentDOM) { for (let i = 0; i < childrenVdom.length; i++) { let childVdom = childrenVdom[i]; childVdom.mountIndex = i; mount(childVdom, parentDOM); }}const ReactDOM = { render,};export default ReactDOM;
16.PortalReact v16增加了对Portal的直接支持- 它可以把
JSX渲染到一个单独的DOM节点中
16.1 src\index.js
src\index.js
import React from './react';import ReactDOM from './react-dom';class Dialog extends React.Component { constructor(props) { super(props); this.node = document.createElement('div'); document.body.appendChild(this.node); } render() { ReactDOM.createPortal( <div className="dialog"> {this.props.children} </div>, this.node ); return null; } componentWillUnmount() { window.document.body.removeChild(this.node); }}class App extends React.Component { render() { return ( <div> <Dialog>模态窗
</Dialog> </div> ) }}ReactDOM.render( <App />, document.getElementById('root'));
16.2 src\react-dom.js
src\react-dom.js
import { REACT_TEXT, REACT_FORWARD_REF_TYPE, PLACEMENT, MOVE, REACT_PROVIDER, REACT_CONTEXT, REACT_MEMO } from "./constants";import { addEvent } from "./event";import React from './react';function render(vdom, container) { mount(vdom, container);}+function createPortal(vdom, container) {+ mount(vdom, container);+}export function mount(vdom, container) { let newDOM = createDOM(vdom);+ if (newDOM) { container.appendChild(newDOM); if (newDOM.componentDidMount) newDOM.componentDidMount();+ }}export function createDOM(vdom) { let { type, props, ref } = vdom; let dom; if (type && type.$$typeof === REACT_MEMO) { return mountMemoComponent(vdom); } else if (type && type.$$typeof === REACT_PROVIDER) { return mountProviderComponent(vdom) } else if (type && type.$$typeof === REACT_CONTEXT) { return mountContextComponent(vdom) } else if (type && type.$$typeof === REACT_FORWARD_REF_TYPE) { return mountForwardComponent(vdom); } else if (type === REACT_TEXT) { dom = document.createTextNode(props.content); } else if (typeof type === "function") { if (type.isReactComponent) { return mountClassComponent(vdom); } else { if (type === React.Fragment) { dom = document.createDocumentFragment(); } else { return mountFunctionComponent(vdom); } } } else { dom = document.createElement(type); } if (props) { updateProps(dom, {}, props); if (typeof props.children == "object" && props.children.type) { props.children.mountIndex = 0; mount(props.children, dom); } else if (Array.isArray(props.children)) { reconcileChildren(props.children, dom); } } vdom.dom = dom; if (ref) ref.current = dom; return dom;}
function mountMemoComponent(vdom) { let { type, props } = vdom; let renderVdom = type.type(props); vdom.prevProps = props; vdom.oldRenderVdom = renderVdom;+ if (!renderVdom) return null; return createDOM(renderVdom);}function mountProviderComponent(vdom) { let { type, props } = vdom; let context = type._context; context._currentValue = props.value; let renderVdom = props.children; vdom.oldRenderVdom = renderVdom;+ if (!renderVdom) return null; return createDOM(renderVdom);}function mountContextComponent(vdom) { let { type, props } = vdom; let context = type._context; let renderVdom = props.children(context._currentValue); vdom.oldRenderVdom = renderVdom;+ if (!renderVdom) return null; return createDOM(renderVdom);}function mountForwardComponent(vdom) { let { type, props, ref } = vdom; let renderVdom = type.render(props, ref); vdom.oldRenderVdom = renderVdom;+ if (!renderVdom) return null; return createDOM(renderVdom);}function mountClassComponent(vdom) { let { type, props, ref } = vdom; let classInstance = new type(props); if (type.contextType) { classInstance.context = type.contextType._currentValue; } vdom.classInstance = classInstance; if (ref) ref.current = classInstance; if (classInstance.componentWillMount) classInstance.componentWillMount(); let renderVdom = classInstance.render(); classInstance.oldRenderVdom = vdom.oldRenderVdom = renderVdom;+ if (!renderVdom) return null; let dom = createDOM(renderVdom); if (classInstance.componentDidMount) dom.componentDidMount = classInstance.componentDidMount.bind(classInstance); return dom;}function mountFunctionComponent(vdom) { let { type, props } = vdom; let renderVdom = type(props); vdom.oldRenderVdom = renderVdom;+ if (!renderVdom) return null; return createDOM(renderVdom);}function updateProps(dom, oldProps, newProps) { for (let key in newProps) { if (key === "children") { continue; } if (key === "style") { let style = newProps[key]; for (let attr in style) { dom.style[attr] = style[attr]; } } else if (key.startsWith("on")) { addEvent(dom, key.toLocaleLowerCase(), newProps[key]); } else { dom[key] = newProps[key]; } }}export function findDOM(vdom) { let { type } = vdom; let dom; if (type && type.$$typeof === REACT_MEMO) { dom = findDOM(vdom.oldRenderVdom); } else if (type && type.$$typeof === REACT_PROVIDER) { dom = findDOM(vdom.oldRenderVdom); } else if (type && type.$$typeof === REACT_CONTEXT) { dom = findDOM(vdom.oldRenderVdom); } else if (typeof type === "function") { if (type === React.Fragment) { return vdom.dom; } else { dom = findDOM(vdom.oldRenderVdom); } } else { dom = vdom.dom; } return dom;}function unMount(vdom) { let currentDOM = findDOM(vdom); if (vdom.classInstance && vdom.classInstance.componentWillUnmount) { vdom.classInstance.componentWillUnmount(); } if (vdom.props && vdom.props.ref) { vdom.props.ref.current = null; } Object.keys(vdom.props).forEach((propName) => { if (propName.slice(0, 2) === "on") { const eventName = propName.toLowerCase().slice(0, 2); currentDOM.removeEventListener(eventName, vdom.props[propName]); } }); if (vdom.props.children) { let children = Array.isArray(vdom.props.children) ? vdom.props.children : [vdom.props.children]; children.forEach(unMount); } currentDOM.parentNode.removeChild(currentDOM);}export function compareTwoVdom(parentDOM, oldVdom, newVdom, nextDOM) { if (!oldVdom && !newVdom) { //老和新都是没有
return; } else if (!!oldVdom && !newVdom) { //老有新没有
unMount(oldVdom); } else if (!oldVdom && !!newVdom) { //老没有新的有
let newDOM = createDOM(newVdom); if (nextDOM) parentDOM.insertBefore(newDOM, nextDOM); else parentDOM.appendChild(newDOM); if (newDOM.componentDidMount) newDOM.componentDidMount(); return; } else if (!!oldVdom && !!newVdom && oldVdom.type !== newVdom.type) { //新老都有,但类型不同
let newDOM = createDOM(newVdom); unMount(oldVdom); if (newDOM.componentDidMount) newDOM.componentDidMount(); } else { updateElement(oldVdom, newVdom); }}function updateElement(oldVdom, newVdom) { if (oldVdom.type && oldVdom.type.$$typeof === REACT_MEMO) { updateMemoComponent(oldVdom, newVdom); } else if (oldVdom.type.$$typeof === REACT_CONTEXT) { updateContextComponent(oldVdom, newVdom); } else if (oldVdom.type.$$typeof === REACT_PROVIDER) { updateProviderComponent(oldVdom, newVdom); } else if (oldVdom.type === REACT_TEXT && newVdom.type === REACT_TEXT) { let currentDOM = newVdom.dom = findDOM(oldVdom); if (oldVdom.props.content !== newVdom.props.content) { currentDOM.textContent = newVdom.props.content; } return; } else if (typeof oldVdom.type === 'string') { let currentDOM = newVdom.dom = findDOM(oldVdom); updateProps(currentDOM, oldVdom.props, newVdom.props); updateChildren(currentDOM, oldVdom.props.children, newVdom.props.children); } else if (typeof oldVdom.type === 'function') { if (oldVdom.type.isReactComponent) { newVdom.classInstance = oldVdom.classInstance; updateClassInstance(oldVdom, newVdom); } else { if (oldVdom.type === React.Fragment) { let currentDOM = newVdom.dom = findDOM(oldVdom); updateChildren(currentDOM, oldVdom.props.children, newVdom.props.children); } else { updateFunctionComponent(oldVdom, newVdom); } } }}function updateMemoComponent(oldVdom, newVdom) { let { type, prevProps } = oldVdom; if (!type.compare(prevProps, newVdom.props)) { let oldDOM = findDOM(oldVdom); let parentDOM = oldDOM.parentNode; let { type, props } = newVdom; let renderVdom = type.type(props); compareTwoVdom(parentDOM, oldVdom.oldRenderVdom, renderVdom); newVdom.prevProps = props; newVdom.oldRenderVdom = renderVdom; } else { newVdom.prevProps = prevProps; newVdom.oldRenderVdom = oldVdom.oldRenderVdom; }}function updateProviderComponent(oldVdom, newVdom) { let parentDOM = findDOM(oldVdom).parentNode; let { type, props } = newVdom; let context = type._context; context._currentValue = props.value; let renderVdom = props.children; compareTwoVdom(parentDOM, oldVdom.oldRenderVdom, renderVdom); newVdom.oldRenderVdom = renderVdom;}function updateContextComponent(oldVdom, newVdom) { let parentDOM = findDOM(oldVdom).parentNode; let { type, props } = newVdom; let context = type._context; let renderVdom = props.children(context._currentValue); compareTwoVdom(parentDOM, oldVdom.oldRenderVdom, renderVdom); newVdom.oldRenderVdom = renderVdom;}function updateFunctionComponent(oldVdom, newVdom) { let parentDOM = findDOM(oldVdom).parentNode; let { type, props } = newVdom; let newRenderVdom = type(props); compareTwoVdom(parentDOM, oldVdom.oldRenderVdom, newRenderVdom); newVdom.oldRenderVdom = newRenderVdom;}function updateClassInstance(oldVdom, newVdom) { let classInstance = newVdom.classInstance = oldVdom.classInstance; newVdom.oldRenderVdom = oldVdom.oldRenderVdom; if (classInstance.componentWillReceiveProps) { classInstance.componentWillReceiveProps(); } classInstance.updater.emitUpdate(newVdom.props);}function updateChildren(parentDOM, oldVChildren, newVChildren) { oldVChildren = Array.isArray(oldVChildren) ? oldVChildren : oldVChildren ? [oldVChildren] : []; newVChildren = Array.isArray(newVChildren) ? newVChildren : newVChildren ? [newVChildren] : []; let keyedOldMap = {}; let lastPlacedIndex = 0; oldVChildren.forEach((oldVChild, index) => { let oldKey = oldVChild.key ? oldVChild.key : index; keyedOldMap[oldKey] = oldVChild; }); let patch = []; newVChildren.forEach((newVChild, index) => { newVChild.mountIndex = index; let newKey = newVChild.key ? newVChild.key : index; let oldVChild = keyedOldMap[newKey]; if (oldVChild) { updateElement(oldVChild, newVChild); if (oldVChild.mountIndex < lastPlacedIndex) { patch.push({ type: MOVE, oldVChild, newVChild, mountIndex: index }); } delete keyedOldMap[newKey]; lastPlacedIndex = Math.max(lastPlacedIndex, oldVChild.mountIndex); } else { patch.push({ type: PLACEMENT, newVChild, mountIndex: index }); } }); let moveVChild = patch.filter(action => action.type === MOVE).map(action => action.oldVChild); Object.values(keyedOldMap).concat(moveVChild).forEach((oldVChild) => { let currentDOM = findDOM(oldVChild); parentDOM.removeChild(currentDOM); }); patch.forEach(action => { let { type, oldVChild, newVChild, mountIndex } = action; let childNodes = parentDOM.childNodes; if (type === PLACEMENT) { let newDOM = createDOM(newVChild); let childNode = childNodes[mountIndex]; if (childNode) { parentDOM.insertBefore(newDOM, childNode); } else { parentDOM.appendChild(newDOM); } } else if (type === MOVE) { let oldDOM = findDOM(oldVChild); let childNode = childNodes[mountIndex]; if (childNode) { parentDOM.insertBefore(oldDOM, childNode); } else { parentDOM.appendChild(oldDOM); } } });}function reconcileChildren(childrenVdom, parentDOM) { for (let i = 0; i < childrenVdom.length; i++) { let childVdom = childrenVdom[i]; childVdom.mountIndex = i; mount(childVdom, parentDOM); }}const ReactDOM = { render, createPortal};export default ReactDOM;参考
React课程 > 来自
<http://www.zhufengpeixun.com/strong/html/106.1.react.html>